package yawning.mew.cat;

import yawning.mew.ProcEffect.Trigger;
import yawning.mew.cat.CatModelSimState.CatTargetData;
import yawning.mew.character.CharacterModel.AttackResult;
import yawning.mew.sim.SimulationAction;
import yawning.mew.sim.SimulationEventAction;
import yawning.mew.sim.SimulationTarget;

public class EventRip extends SimulationEventAction {
	CatModelSimState mState;
	private SimulationTarget mTarget;
	private CatTargetData mTargetData;
	private String mTargetName;
	private double mAttackPower;
	private double mPCrit;
	private double mMasteryMult;
	private double mToTTMult;
	private int mCPs;
	int mGlyphExts;
	boolean mTigersFury;
	boolean mBloodInTheWaterRefresh;

	EventRip(CatModelSimState state, CatTargetData targetData) {
		super(state.mSimEngine);
		mState = state;
		mTarget = targetData.getSimulationTarget();
		mTargetData = targetData;
		mTargetName = mTarget.getTarget().getName();
	}

	@Override
	public double perform() {
		int cost = mState.getEnergyCost(mState.mModel.ripCost);
		int cps = mState.getComboPoints();
		if ((mState.getEnergy() < cost && !mState.mOocEv.isUp()) || cps == 0) return SimulationAction.ACTION_FAIL;
		int prevEnergy = mState.getEnergy(); int prevCps = cps;

		AttackResult result = (mTargetData.mCanParry) ? mState.mModel.getFrontalYellowResult() : mState.mModel.getYellowResult();

		// Rip refresh behavior is inconsistent.
		if (isUp() && (mCPs > cps || mAttackPower > mState.mModel.getAttackPower())) {
			// "A more powerful spell is already in effect."
			// It checks AP and CPs, but not Tiger's Fury.  (Crit is an educated guess.)
			return SimulationAction.ACTION_FAIL;
		}

		if (result == AttackResult.MISS) {
			cost = (int) Math.round(cost * 0.2);
			mNrRipMiss++;

			if (mState.mIsLogging) mState.mLog.log("Rip: %s MISS\n", mTargetName);
		} else if (result == AttackResult.DODGE) {
			cost = (int) Math.round(cost * 0.2);
			mNrRipDodge++;

			if (mState.mIsLogging) mState.mLog.log("Rip: %s DODGE\n", mTargetName);
		} else if (result == AttackResult.PARRY) {
			cost = (int) Math.round(cost * 0.2);
			mNrRipParry++;

			if (mState.mIsLogging) mState.mLog.log("Rip: %s PARRY\n", mTargetName);
		} else {
			mNrRipHit++;
			mGlyphExts = 0;
			mCPs = cps;
			mNrRipCPs += cps;
			mMasteryMult = mState.mModel.getBleedMastery();
			mAttackPower = mState.mModel.getAttackPower();
			mPCrit = mState.mModel.getMeleeCritChance();
			mTigersFury = mState.mTigersFuryEv.isUp();
			mToTTMult = mState.mTottEv.getMult();
			mBloodInTheWaterRefresh = false;

			if (mState.mIsLogging) mState.mLog.log("Rip: %s HIT\n", mTargetName);

			if (!isUp())
				schedule(mState.mModel.getRipDebuffDuration(), mState.mModel.getRipTickInterval());
			else
				extendExpiry(mState.mModel.getRipDebuffDuration() + getNextTickTime() - getExpiryTime());

			mState.mBerserkEv.onTier12_4pc_tigger(cps);

			mState.mSimEngine.triggerProc(mTarget, Trigger.YELLOW);

			mState.onFinisher();
		}

		if (mState.mOocEv.isUp()) mNrRipOoC++;
		mRipEnergy += mState.onOoCConsumer(cost);

		mState.logResourceChange(prevEnergy, prevCps);

		return 1.0;
	}

	@Override
	public void onTick() {
		AttackResult result = mState.mModel.getDotResult(mPCrit);
		double damage = mState.mModel.levelDep.getRipBaseDmg() + mState.mModel.levelDep.getRipCpDmg() * mCPs + mAttackPower * mState.mModel.levelDep.getRipCoeff() * mCPs;
		damage *= mState.mModel.multRipGlyph;

		if (result == AttackResult.CRIT) {
			mNrRipDotCrit++;
			damage *= mState.mModel.multCrit;
		} else {
			mNrRipDotHit++;
		}

		damage *= mState.getBleedDamageMult(mMasteryMult, mTargetData);
		damage *= (mTigersFury) ? mState.mModel.multTigersFury : 1.0;
		damage *= mToTTMult;
		if (mTigersFury) mNrRipDotTFTicks++;
		mRipDamage += Math.round(damage);

		if (mState.mIsLogging) mState.mLog.log("Rip Tick: %s %s Damage: %d\n", mTargetName, result, Math.round(damage));

		if (result == AttackResult.CRIT) mState.mSimEngine.triggerProc(mTarget, Trigger.DOTCRIT); else mState.mSimEngine.triggerProc(mTarget, Trigger.DOT);
	}

	@Override
	public void onExpire() {
		if (mState.mIsLogging) mState.mLog.log("Rip: %s Expiration\n", mTargetName);
	}

	public void onGlyphTrigger() {
		if (mGlyphExts < mState.mModel.ripGlyphExtMaxTicks && mState.mModel.ripGlyphExtDuration > 0) {
			if (mState.mIsLogging) mState.mLog.log("Rip: %s Glyph of Bloodletting: %d / 3\n", mTargetName, mGlyphExts + 1);

			mGlyphExts++;

        	if (mState.mModel.bug_glyphOfShred_SimCraft) {
        		// Model Glyph of Shred as 1/1/2 ticks like Simulation Craft.
        		//
        		// This is incorrect as 11, 12 and 13 ticks have been observed, however
        		// it potentially is a better approximation of how Glyph of Shred actually
        		// behaves.
        		//
        		// See: http://elitistjerks.com/f73/t110356-feral_mew_simulator/p3/#post1886910
        		if (mGlyphExts == 3) {
        			extendExpiry(mState.mModel.ripGlyphExtDuration);
        		}

        		extendExpiry(mState.mModel.ripGlyphExtDuration);
        	} else if (mState.mModel.bug_glyphOfShred_3Sec){
        		// Model Glyph of Shred as +3 sec, randomly expiring at 24/26 secs to resolve
        		// the nonsensical 25 sec Rip case.
        		//
        		// This is also potentially incorrect as at one point 11 ticks were observed, however
        		// I'm not certain if this is the case.
        		//
        		// See: http://elitistjerks.com/f73/t123596-feral_cat_4_2_fire_cat_funtimes/p9/#post2047778
        		if (mGlyphExts < 3) {
        			extendExpiry(mState.mModel.ripGlyphExtDuration);
        		} else {
        			extendExpiry(mState.mSimEngine.getRandom().nextBoolean() ? 2 : 4);
        		}
        	} else {
        		// Model Glyph of Shred as the tooltip would suggest.
        		//
        		// This is likewise potentially incorrect since the glyph has displayed inconsistent
        		// behavior to that stated on the tooltip since Cataclysm release if not earlier.
        		extendExpiry(mState.mModel.ripGlyphExtDuration);
        	}
		}
	}

	public void onBite() {
		if (mTargetData.mBloodInTheWaterEv.mInRange && mState.mModel.pBloodInTheWater > 0) {
			if (mState.mModel.pBloodInTheWater == 1.0 || mState.mSimEngine.getRandom().nextBoolean(mState.mModel.pBloodInTheWater)) {
				// I'm not certain exactly how this works.
				//
				// For now assume that it re-snapshot stats immediately, and does not
				// affect the tick timer.

				if (mState.mIsLogging) mState.mLog.log("Rip: %s Blood in the Water\n", mTargetName);

				mGlyphExts = 0; // As of  4.0.6, Glyph of Shred is reset.
				//mCPs = 5; // Per 4.0.1, refresh behavior preserves the number of cps.
				mMasteryMult = mState.mModel.getBleedMastery();
				mAttackPower = mState.mModel.getAttackPower();
				mPCrit = mState.mModel.getMeleeCritChance();
				mTigersFury = mState.mTigersFuryEv.isUp();
				mBloodInTheWaterRefresh = true;

				extendExpiry(mState.mModel.getRipDebuffDuration() + getNextTickTime() - getExpiryTime());
			}
		}
	}

	public void onBitW() {
		mRipDamageBase = mRipDamage;
		mNrRipBase = mNrRipHit;
		mNrRipFailBase = mNrRipMiss + mNrRipDodge + mNrRipParry;
	}

	long mRipDamage = 0;
	int mNrRipMiss = 0;
	int mNrRipDodge = 0;
	int mNrRipParry = 0;
	int mNrRipHit = 0;
	int mNrRipDotCrit = 0;
	int mNrRipDotHit = 0;
	int mNrRipDotTFTicks = 0;

	long mRipDamageBase = 0;
	int mNrRipBase = 0;
	int mNrRipFailBase = 0;

	int mNrRipCPs;
	int mRipEnergy;
	int mNrRipOoC;
}
